package edu.fsu.cs.buzbee.steggo;

import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;



public class Decoder {
	
	BufferedImage image;
	int bits = 1;
	
	long offset;
	
	/**
	 * Decodes a hidden data file from a secret image file
	 * @param secret File containing hidden data
	 */
	public Decoder(BufferedImage secret)
	{
		if (secret == null)
			throw new NullPointerException("Image cannot be null");
		if (secret.getWidth() * secret.getHeight() == 0)
			throw new NullPointerException("Image contains 0 pixels");
		
		image = secret;
	}
	
	/**
	 * Get number of LSBs per RGB component interpreted as data
	 * @return
	 */
	public int getEncodeBits() { return bits; }
	/**
	 * Set number of LSBs per RGB component interpreted as data
	 * @param bitc Number of LSBs, between 1 and 8
	 */
	public void setEncodeBits(int bitc) {
		if (bitc < 1 || bitc > 8)
			throw new IndexOutOfBoundsException("Bit count must be between 1 and 8");
		this.bits = bitc;
	}
	
	Point linearToPoint(long offset)
	{
		long y = offset / image.getWidth();
		long x = offset - (image.getWidth()*y);
		
		return new Point((int)x, (int)y);
	}
	
	/**
	 * Recovers data from the image
	 * @param target Location to save extracted data
	 * @throws IOException Thrown when there was an error writing to a data file
	 * @throws InvalidFormatException Thrown when the image is not in a valid format
	 */
	public void decode(File target) throws IOException, InvalidFormatException
	{

		//decodeByte();
		for (bits = 1; bits <= 8; ++bits) 
		{
			if (decodeInt() == Encoder.MAGIC)
				break;
			reset();
		}
		reset();
		
		if (decodeInt() != Encoder.MAGIC)
		{
			throw new InvalidFormatException("The file image given does not appear to contain a hidden message.");
		}
		long filesize = decodeInt();

		
		OutputStream out = new FileOutputStream(target);
		for (long br = 0; br < filesize; ++br)
		{
			out.write(decodeByte());
		}
		out.close();
		
	}
	
	/**
	 * Sets the current position in the image to be 0 again
	 */
	public void reset() { offset = 0; }
	
	/**
	 * Decodes a little-endian byte
	 * @return
	 */
	public int decodeInt()
	{
		int result = 0;
		result = decodeByte();
		result |= (decodeByte() << 8);
		result |= (decodeByte() << 16);
		result |= (decodeByte() << 24);
		return result;
	}
	
	/**
	 * Decodes 1 byte starting at the pixel at the current offset.  Updates offset afterwards to be offset + Ceil(8.0/bits*3)
	 * @return The decoded byte
	 */
	public int decodeByte()
	{
		if (image == null)
			throw new NullPointerException("Base image not set."); 
		long max_size = image.getWidth() * image.getHeight();
		
		if (offset + Math.ceil(8.0/(bits*3)) >= max_size)
				throw new IndexOutOfBoundsException("Cannot decode a byte at this offset - will read past last pixel");
		
		int bits_to_decode = 8;
		
		int data = 0;
		int effective_bits;
		//System.out.println("----Byte Decode----");
		while (bits_to_decode > 0)
		{
			Point p = linearToPoint(offset++);
			int rgb = image.getRGB(p.x, p.y);
			//System.out.printf("Decoding pixel %X with %d bits\n", rgb, bits);
				
			int r = (rgb & 0x00FF0000) >>> 16;
			//System.out.printf("R=%x\n", r);
			effective_bits = Math.min(bits, bits_to_decode);
			r <<= (32 - effective_bits); r >>>= (32 - effective_bits);
			//System.out.printf("The lower %d bits of R are: %X\n",effective_bits, r);
			r <<= (8 - bits_to_decode);
			data |= r;
			bits_to_decode -= effective_bits;
			if (bits_to_decode <= 0)
				break;
			
			int g = (rgb & 0x0000FF00) >>> 8;
			effective_bits = Math.min(bits, bits_to_decode);
			//System.out.printf("G=%x\n", g);
			g <<= (32 - effective_bits); g >>>= (32 - effective_bits);
			//System.out.printf("The lower %d bits of G are: %X\n",effective_bits, g);
			g <<= (8 - bits_to_decode);
			data |= g;
			bits_to_decode -= effective_bits;
			if (bits_to_decode <= 0)
				break;
			
			int b = (rgb & 0x000000FF);
			effective_bits = Math.min(bits, bits_to_decode);
			//System.out.printf("B=%x\n", b);
			b <<= (32 - effective_bits); b >>>= (32 - effective_bits);
			//System.out.printf("The lower %d bits of B are: %X\n",effective_bits, b);
			b <<= (8 - bits_to_decode);
			data |= b;
			bits_to_decode -= effective_bits;
			if (bits_to_decode <= 0)
				break;
			
			
			
		}
		//System.out.printf("----Decoded byte %X-----\n", data);
		return data;
		
		
	}
	
}
